/*************************************************************************** 
 * (c) 2006 Dmitry Osinovsky, osdmsoftware@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License as        *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Collections;
using System.Diagnostics;
#if PocketPC
using Microsoft.WindowsCE.Forms;
#endif
using System.Drawing;


namespace OsdmSoftware.CommonLibrary
{
    public interface IPropertyEditor
    {
        Control CreateEditor(PropertyEditors pe, string propertyName, string propertyLabel, Type propType,
            object obj, object value, Control parent, int top, out int height);
        object GetValueFromEditor(Control editor, string propertyName, object obj, out bool OK);
        bool SupportsInherited();
        bool RequiresKeyboardInput();
    }

    public class BaseEditor
    {
        Type type;

        public Type Type { get { return type; } set { type = value; } }

        public static int MeasureStringHeight(Control ctrl, string text, Font font)
        {
            Bitmap bmp = new Bitmap(1, 1);
            Graphics gr = Graphics.FromImage(bmp);
            //Graphics gr = ctrl.CreateGraphics();
            int h = (int) gr.MeasureString(text, font).Height;
            h = (int) Math.Round(h * 96F / gr.DpiY);
            gr.Dispose();
            bmp.Dispose();
            return h;
        }
    }

    public class EditThisPropertyAttribute: Attribute
    {
        public EditThisPropertyAttribute() { }
    }

    public class LabelAttribute: Attribute
    {
        string label;

        public string Label { get { return label; } set { label = value; } }

        public LabelAttribute(string label) { this.label = label;  }
    }

    public class EditorAttribute: Attribute
    {
        string editorTypeName;

        public string EditorTypeName { get { return editorTypeName; } set { editorTypeName = value; } }

        public EditorAttribute(string editorTypeName)
        {
            this.editorTypeName = editorTypeName;
        }
    }

    public interface IObjectEditor
    {
        void EditThisObject(object obj, PropertyEditors pe);
    }

    public class TextBoxPropertyEditor: BaseEditor, IPropertyEditor
    {
        public TextBoxPropertyEditor(Type type) { this.Type = type; }

        public Control CreateEditor(PropertyEditors pe, string propertyName, string propertyLabel, Type propType, object obj,
            object value, Control parent, int top, out int height)
        {
            Label lbl = new Label();
            lbl.Text = propertyLabel;
            lbl.Top = top;
            lbl.Left = 1;
            //lbl.Height = 25;
            lbl.Width = 100;
            parent.Controls.Add(lbl);

            TextBox text = new TextBox();
            if (value == null)
                text.Text = "";
            else
                text.Text = value.ToString();
            text.Top = top;
            text.Left = lbl.Width + lbl.Left + 1;
            parent.Controls.Add(text);

            int h = MeasureStringHeight(parent, propertyLabel, lbl.Font);
            height = Math.Max(20, h);
            lbl.Height = h;

            return text;
        }

        public object GetValueFromEditor(Control editor, string propertyName, object obj, out bool OK)
        {
            TextBox text = (TextBox) editor;
            object result = null;
            try
            {
                result = Convert.ChangeType(text.Text, Type, null);
            }
            catch
            {
                MessageBox.Show("Cannot convert " + text.Text + " to type " + Type.Name);
                OK = false;
                return null;
            }

            //MessageBox.Show(propertyName + "=" + result.ToString());
            OK = true;

            return result;
        }

        public bool SupportsInherited()
        {
            return false;
        }

        public bool RequiresKeyboardInput()
        {
            return true;
        }
    }

    public class ListEditor: BaseEditor, IPropertyEditor
    {
        IList list;

        public IList List { get { return list; } set { list = value; } }

        IList labelList;

        public IList LabelList { get { return labelList; } set { labelList = value; } }

        bool supportInherited;

        public bool SupportInherited { get { return supportInherited; } set { supportInherited = value; } }

        public ListEditor(Type type, IList list, bool supportInherited): this(type, list, list, supportInherited)
        {            
        }

        public ListEditor(Type type, IList list, IList labelList, bool supportInherited)
        {
            this.Type = type;
            this.list = list;
            this.labelList = labelList;
            this.SupportInherited = supportInherited;
        }

        public Control CreateEditor(PropertyEditors pe, string propertyName, string propertyLabel, Type propType,
            object obj, object value, Control parent, int top, out int height)
        {
            Trace.Assert(list.Count == labelList.Count);

            Label lbl = new Label();
            lbl.Text = propertyLabel;
            lbl.Top = top;
            lbl.Left = 1;
            //lbl.Height = 25;
            lbl.Width = 100;
            parent.Controls.Add(lbl);

            ComboBox combo = new ComboBox();
            combo.BeginUpdate();
            for (int i = 0; i < list.Count; i++)
            {
                object o = list[i];
                if (propType.IsAssignableFrom(o.GetType()))
                {
                    combo.Items.Add(labelList[i]);
                }
            }
            combo.EndUpdate();
            combo.SelectedIndex = list.IndexOf(value);

            combo.Top = top;
            combo.Left = lbl.Width + lbl.Left + 1;
            parent.Controls.Add(combo);

            int h = MeasureStringHeight(parent, propertyLabel, lbl.Font);
            height = Math.Max(20, h);
            lbl.Height = h;

            return combo;
        }

        public object GetValueFromEditor(Control editor, string propertyName, object obj, out bool OK)
        {
            ComboBox combo = (ComboBox) editor;
            object result = null;

            if (combo.SelectedIndex != -1)
                result = list[combo.SelectedIndex];
            OK = true;

            return result;
        }

        public bool SupportsInherited()
        {
            return supportInherited;
        }

        public bool RequiresKeyboardInput()
        {
            return false;
        }
    }

    public class EnumEditor: ListEditor
    {
        public EnumEditor(Type type): base(type, null, false)
        {
            Trace.Assert(type.IsEnum, "EnumEditor can only edit enumeration types");
            ArrayList valueList = new ArrayList();
            ArrayList labelList = new ArrayList();

            FieldInfo[] fa = type.GetFields();
            foreach (FieldInfo fi in fa)
            {
                if (fi.IsLiteral)
                {
                    string propLabel = fi.Name;
                    LabelAttribute la = (LabelAttribute) Attribute.GetCustomAttribute(fi, typeof(LabelAttribute));
                    if (la != null)
                        propLabel = la.Label;

                    valueList.Add(fi.GetValue(null));
                    labelList.Add(propLabel);
                }
            }

            List = valueList;
            LabelList = labelList;
        }
    }

    public class CheckBoxEditor: BaseEditor, IPropertyEditor
    {
        public CheckBoxEditor()
        {
            this.Type = typeof(bool);
        }

        public Control CreateEditor(PropertyEditors pe, string propertyName, string propertyLabel, Type propType,
            object obj, object value, Control parent, int top, out int height)
        {
            CheckBox cb = new CheckBox();
            //cb.Text = propertyLabel;
            cb.Top = top;
            cb.Left = 1;
            cb.Width = 20;
            cb.Checked = (bool) value;
            parent.Controls.Add(cb);

            Label lbl = new Label();
            lbl.Text = propertyLabel;
            lbl.Top = top;
            lbl.Left = 21;
            //lbl.Height = 25;
            lbl.Width = 200;
            parent.Controls.Add(lbl);

            int h = MeasureStringHeight(parent, propertyLabel, lbl.Font);
            height = Math.Max(20, h);
            lbl.Height = h;

            /*height = cb.Height;
            int h = MeasureStringHeight(parent, propertyLabel, cb.Font);
            height = Math.Max(20, h);
            cb.Height = height;*/
            return cb;
        }

        public object GetValueFromEditor(Control editor, string propertyName, object obj, out bool OK)
        {
            CheckBox cb = (CheckBox) editor;
            OK = true;
            return cb.Checked;
        }

        public bool SupportsInherited()
        {
            return false;
        }

        public bool RequiresKeyboardInput()
        {
            return false;
        }
    }

    public class ObjectPropEditor: BaseEditor, IPropertyEditor
    {
        public Control CreateEditor(PropertyEditors pe, string propertyName, string propertyLabel,
            Type propType, object obj, object value, Control parent, int top, out int height)
        {
            ObjectEditor oe = new ObjectEditor(pe, propertyName, propertyLabel, propType, obj, value);
            oe.Top = top;
            parent.Controls.Add(oe);
            height = oe.Height;
            return oe;
        }

        public object GetValueFromEditor(Control editor, string propertyName, object obj, out bool OK)
        {
            OK = true;
            return ((ObjectEditor) editor).value;
        }

        public bool SupportsInherited()
        {
            return true;
        }

        public bool RequiresKeyboardInput()
        {
            return false;
        }
    }

    public class NestedButton: Button
    {
        PropertyEditors propertyEditors;

        public PropertyEditors PropertyEditors { get { return propertyEditors; } set { propertyEditors = value; } }

        object nestedObject;

        public object NestedObject { get { return nestedObject; } set { nestedObject = value; } }

        public NestedButton() { }

        protected override void OnClick(EventArgs e)
        {
            if (nestedObject is IList)
            {
                Type t = nestedObject.GetType();
                Type t1 = t.GetGenericArguments()[0];
                propertyEditors.EditList((IList) nestedObject, t1);
            }
            else
            {
                propertyEditors.EditObject(nestedObject);
            }
            //base.OnClick(e);
        }
    }

    public class TypeTreeNode
    {
        public Type Type;
        public bool CanBeInstantiated { get { return (Type.IsClass) && (!Type.IsAbstract); } }
        public List<TypeTreeNode> SubTypes = new List<TypeTreeNode>();
        public void AddSubTypes(IList list)
        {
            if (CanBeInstantiated)
            {
                list.Add(Type);
            }

            foreach (TypeTreeNode ttn in SubTypes)
            {
                ttn.AddSubTypes(list);
            }
        }
    }

    public class TypeTree
    {
        Dictionary<Type, TypeTreeNode> list = new Dictionary<Type, TypeTreeNode>();
        public Dictionary<Type, TypeTreeNode> List { get { return list; } }
        public static TypeTree theTree = new TypeTree();

        public TypeTreeNode AddType(Type mostParentType, Type type)
        {
            TypeTreeNode ttn;
            if (list.TryGetValue(type, out ttn))
            {
                return ttn;
            }

            ttn = new TypeTreeNode();
            ttn.Type = type;
            list.Add(type, ttn);

            if (mostParentType != type)
            {
                TypeTreeNode ttn1;
                if (mostParentType.IsInterface)
                    ttn1 = AddType(mostParentType, mostParentType);
                else
                    ttn1 = AddType(mostParentType, type.BaseType);
                ttn1.SubTypes.Add(ttn);
            }

            return ttn;
        }

        public List<Type> GetDerivedTypes(Type type)
        {
            List<Type> l = new List<Type>();
            TypeTreeNode ttn;
            if (!list.TryGetValue(type, out ttn))
            {
                l.Add(type);
            }
            else
            {
                ttn.AddSubTypes(l);
            }
            return l;
        }
    }

    public class PropertyEditors
    {
        Stack<Form> forms = new Stack<Form>();
        Dictionary<Type, IPropertyEditor> mappings = new Dictionary<Type, IPropertyEditor>();
        public Dictionary<Type, IPropertyEditor> Mappings { get { return mappings; } }

        bool editOnlyMarkedAsEditable;

        public bool EditOnlyMarkedAsEditable { get { return editOnlyMarkedAsEditable; } set { editOnlyMarkedAsEditable = value; } }

        public PropertyEditors()
        {
            mappings.Add(typeof(Int32), new TextBoxPropertyEditor(typeof(Int32)));
            mappings.Add(typeof(String), new TextBoxPropertyEditor(typeof(String)));
            mappings.Add(typeof(Double), new TextBoxPropertyEditor(typeof(Double)));
            mappings.Add(typeof(bool), new CheckBoxEditor());
        }

        class Editor
        {
            public IPropertyEditor PropertyEditor;
            public string PropertyName;
            public Control Control;
            public PropertyInfo PropertyInfo;

            public Editor(IPropertyEditor PropertyEditor, string PropertyName, Control Control, PropertyInfo PropertyInfo)
            {
                this.PropertyEditor = PropertyEditor;
                this.PropertyName = PropertyName;
                this.Control = Control;
                this.PropertyInfo = PropertyInfo;
            }
        }

        public bool EditList(IList list, Type type)
        {
            List<Type> l = TypeTree.theTree.GetDerivedTypes(type);
            Props p = new Props(list, l.ToArray(), this);
#if !PocketPC
            p.ShowInTaskbar = false;
#endif
            p.ShowDialog();
            return true;
        }

        /*public bool EditList(IList list, Type[] types)
        {
            Props p = new Props(list, types, this);
            p.ShowDialog();
            return true;
        }*/

        public bool EditObject(object obj)
        {
            Type t = obj.GetType();
            EditorAttribute editorAttr = (EditorAttribute) Attribute.GetCustomAttribute(t, typeof(EditorAttribute));
            if (editorAttr != null)
            {
                Type editorType = Type.GetType(editorAttr.EditorTypeName);
                if (editorType == null)
                {
                    MessageBox.Show("Cannot find editor type '" + editorAttr.EditorTypeName + "' which is set for type " + t.FullName);
                    return true;
                }

                ConstructorInfo c = editorType.GetConstructor(new Type[0]);
                object editor = c.Invoke(null);
                if (!(editor is IObjectEditor))
                {
                    MessageBox.Show("Editor does not support IObjectEditor! Editor type is '" + editorAttr.EditorTypeName + "', it is set for type " + t.FullName);
                    return true;
                }

                IObjectEditor objectEditor = (IObjectEditor) editor;
                objectEditor.EditThisObject(obj, this);

                return true;
            }

            Form currentForm = new Form();
            forms.Push(currentForm);
            Panel pnl = new Panel();
            currentForm.SuspendLayout();
            currentForm.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
            currentForm.AutoScaleMode = AutoScaleMode.Dpi;
#if PocketPC
            currentForm.Width = 240;
#else
            currentForm.Width = 300;
#endif
            //f.AutoScroll = true;
            pnl.AutoScroll = true;
            //pnl.Bounds = currentForm.ClientRectangle;
            pnl.Dock = DockStyle.Fill;
            //pnl.Width = 240;
            bool needKeyboard = false;
            currentForm.Menu = new System.Windows.Forms.MainMenu();
            List<Editor> l = new List<Editor>();
            int top = 1;
            PropertyInfo[] props = t.GetProperties();
            foreach (PropertyInfo p in props)
            {
                if (editOnlyMarkedAsEditable)
                {
                    EditThisPropertyAttribute ea = (EditThisPropertyAttribute) Attribute.GetCustomAttribute(p, typeof(EditThisPropertyAttribute));
                    if (ea == null)
                    {
                        continue;
                    }
                }

                SerializeAsNestedAttribute sa = (SerializeAsNestedAttribute) Attribute.GetCustomAttribute(p, typeof(SerializeAsNestedAttribute));
                if (sa != null)
                {
                    //MessageBox.Show("qq");
                    NestedButton btn = new NestedButton();
                    btn.Top = top;
                    btn.Width = 200;
                    btn.Text = p.Name;
                    btn.PropertyEditors = this;
                    btn.NestedObject = p.GetValue(obj, null);
                    pnl.Controls.Add(btn);
                    top += btn.Height + 2;
                    continue;
                }

                if (!(p.CanWrite && p.CanRead))
                {
                    if (editOnlyMarkedAsEditable)
                    {
                        Trace.Assert(false, "Property " + p.Name + " in class " + t.FullName + " is marked as editable, but has no \"get\" or \"set\" clause. Maybe it should be marked with SerializeAsNestedAttribute?");
                    }
                    continue;
                }

                Type pt = p.PropertyType;
                IPropertyEditor pe;
                if (Attribute.GetCustomAttribute(p, typeof(SerializeAsObjectAttribute)) != null)
                {
                    pe = new ObjectPropEditor();
                }
                else if (!mappings.TryGetValue(pt, out pe))
                {
                    bool r = false;
                    foreach (KeyValuePair<Type, IPropertyEditor> kvp in mappings)
                    {
                        if (kvp.Value.SupportsInherited() && kvp.Key.IsAssignableFrom(pt))
                        {
                            pe = kvp.Value;
                            r = true;
                            break;
                        }
                    }
                    if (!r)
                    {
                        if (pt.IsEnum)
                        {
                            pe = new EnumEditor(pt);
                        }
                        else
                        {
                            if (editOnlyMarkedAsEditable)
                            {
                                Trace.Assert(false, "Property " + p.Name + " in class " + t.FullName + " is marked as editable, but its type has no property editor. Maybe it should be marked with SerializeAsObjectAttribute?");
                            }
                            continue;
                        }
                    }
                }

                string propLabel = p.Name;
                LabelAttribute la = (LabelAttribute) Attribute.GetCustomAttribute(p, typeof(LabelAttribute));
                if (la != null)
                    propLabel = la.Label;

                int h;
                Control c = pe.CreateEditor(this, p.Name, propLabel, pt, obj, p.GetValue(obj, null), pnl, top, out h);
                needKeyboard = needKeyboard || pe.RequiresKeyboardInput();
                l.Add(new Editor(pe, p.Name, c, p));
                top += h + 2;
            }

#if PocketPC
            InputPanel ip = new InputPanel();
            currentForm.Tag = ip;
            ip.EnabledChanged += new EventHandler(InputPanel_EnabledChanged);
#endif
            /*Panel pnl1 = new Panel();
                pnl1.Top = top;
                pnl1.Height = Screen.PrimaryScreen.Bounds.Height / 2;

                pnl.Controls.Add(pnl1);*/
            currentForm.Controls.Add(pnl);
            //f.PerformAutoScale();
            currentForm.ResumeLayout(false);
#if !PocketPC
            currentForm.ShowInTaskbar = false;
#endif

            bool OK = false;
            while (! OK)
            {
                currentForm.ShowDialog();
                OK = true;
                foreach (Editor e in l)
                {
                    object value = e.PropertyEditor.GetValueFromEditor(e.Control, e.PropertyName, obj, out OK);
                    if (!OK)
                    {
                        break;
                    }
                    e.PropertyInfo.SetValue(obj, value, null);
                }

                if (OK)
                {
                    if (obj is IFinalizeAndValidate)
                    {
                        OK = ((IFinalizeAndValidate) obj).FinalizeAndValidate();
                    }
                }
            }

            currentForm.Dispose();
            currentForm = null;
            forms.Pop();
#if PocketPC
            ip.EnabledChanged -= InputPanel_EnabledChanged;
            ip.Dispose();
            ip = null;
#endif
            return true;
        }

#if PocketPC
        void InputPanel_EnabledChanged(object sender, EventArgs e)
        {
            Form currentForm = forms.Peek();
            if (((InputPanel) (currentForm.Tag)).Enabled)
            {
                currentForm.Controls[0].Dock = DockStyle.None;
                currentForm.Controls[0].Height = currentForm.ClientSize.Height - ((InputPanel) (currentForm.Tag)).Bounds.Height;
            }
            else
            {
                //currentForm.Controls[0].Height = currentForm.ClientSize.Height;
                currentForm.Controls[0].Dock = DockStyle.Fill;
            }
        }
#endif
    }
}
